package com.kamistoat.ruoyi.ruoyicommonsecurity.interceptor;

import com.kamistoat.ruoyi.ruoyiapisystem.feign.RemoteUserService;
import com.kamistoat.ruoyi.ruoyiapisystem.model.LoginUser;
import com.kamistoat.ruoyi.ruoyicommoncore.constant.SecurityConstants;
import com.kamistoat.ruoyi.ruoyicommoncore.context.SecurityContextHolder;
import com.kamistoat.ruoyi.ruoyicommoncore.domain.R;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.JwtUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.ServletUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.SpringUtils;
import com.kamistoat.ruoyi.ruoyicommoncore.utils.StringUtils;
import com.kamistoat.ruoyi.ruoyicommonsecurity.auth.AuthUtil;
import com.kamistoat.ruoyi.ruoyicommonsecurity.utils.SecurityUtils;
import com.kamistoat.ruoyi.ruoyicommonsecurity.utils.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 自定义请求拦截头。就和商城项目中自制的登录验证器是一样的。
 * 本质上就是从请求中提取出 jwt简易令牌，找到缓存中对应的 核心信息令牌。注意是 核心信息令牌，不是 后端登录令牌
 * 然后保存到ThreadLocal中，然后对于这个请求的整条链路中，所有的用户认证都是基于从 ThreadLocal中提取 核心信息令牌 来做。
 * <p>
 * 注意这个拦截器并没有做令牌正确与否的验证，那是因为验证正确与否是在 Gateway模块中已经完成了。
 * 这里就是直接把 令牌转存到 ThreadLocal 中。
 */
@Component
public class TokenInterceptor implements AsyncHandlerInterceptor {
    @Autowired
    AuthUtil authUtil;
    @Autowired
    RemoteUserService remoteUserService;
    @Autowired
    TokenUtils tokenUtils;

    /**
     * 前置处理器。
     *
     * @param request  请求
     * @param response 响应
     * @param handler  处理器
     * @return 是否放行。如果返回true则放行，如果返回false则不放行
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 不是很懂这里的意思。如果不是MVC请求直接放行
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        // 直接放行更新用户核心令牌缓存的内部feign请求
        // 注意，由于拦截器外还有一层网关的存在，所以即便在这里放行，也是无法直接访问 /user/info/byId/ 来到达数据库的，仍会被网关拦截的
        String requestURI = request.getRequestURI();
        if(new AntPathMatcher().match("/user/info/byId/*", requestURI)){
            return true;
        }

        // TODO 这里很疑惑，你前端的header中到底都有啥啊，既然已经有 authorization的Jwt数据了，为啥还要额外携带独立的 id/username/key
        SecurityContextHolder.setUserId(
                // 从当前请求头中提取出 user_id 对应的value
                // 然后保存到 ThreadLocal中
                ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID)
        );
        SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
        SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));

        // 从当前请求头中提取出 Jwt简易用户令牌
        String jwtToken = SecurityUtils.getJwtToken();
        if (StringUtils.isNotEmpty(jwtToken)) {
            // 根据前端传递的 简易用户令牌 去缓存中寻找对应的 核心信息令牌
            LoginUser cacheCoreLoginUser = authUtil.getCacheCoreLoginUser(jwtToken);
            if (StringUtils.isNotNull(cacheCoreLoginUser)) {
                // 直接向 ThreadLocal中转存一份 核心信息令牌
                SecurityContextHolder.set(SecurityConstants.LOGIN_USER, cacheCoreLoginUser);
            } else {
                // 如果 核心信息令牌 过期(毕竟核心信息令牌不自动延期)，则重新制作一份存进缓存和ThreadLocal
                // TODO 严重问题死循环。Feign又被这里拦截，又去调Feign。
                //  要么把业务代码改为更新核心令牌代码为 删除并更新
                //  要么在拦截器添加白名单，放行/user/info/byId/*
                R<LoginUser> coreLoginUserR = remoteUserService.getUserInfoByUserId(Long.valueOf(JwtUtils.getUserId(jwtToken)), SecurityConstants.INNER);
                LoginUser coreLoginUser = coreLoginUserR.getData();
                tokenUtils.refreshCoreLoginUser(coreLoginUser);
                SecurityContextHolder.set(SecurityConstants.LOGIN_USER, coreLoginUser);
            }
        }
        // 如果请求头中根本不携带JWT，说明此时是网关直接放行的login/register, 则拦截器同样直接放行
        return true;
    }


    /**
     * 当请求方法执行完毕，且视图也完成渲染后执行.
     * 清除 ThreadLocal中保存的用户信息
     *
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        SecurityContextHolder.remove();
    }
}
